/****************************************************************************************/
/* MediaFrame is an Open Source streaming media platform in Java                        */
/* which provides a fast, easy to implement and extremely small applet                  */
/* that enables to view your audio/video content without having                         */
/* to rely on external player applications or bulky plug-ins.                           */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* "DataBuffer.java"                                                                    */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* We changed a lot of code and added a lot of functionality.							*/
/* This includes, but not limited to, the following changes:							*/
/* 1. The project was renamed to MediaFrame;											*/
/* 2. The connection speed detection procedure was added;								*/
/* 3. The JavaScript API functions were added;											*/
/* 4. The pre and post image support functionality was added;							*/
/* 5. The ability to save movie into the local disk was added;							*/
/* 6. The inner buffer for a movie file was added;										*/
/* 7. The click-through functionality was added;    									*/
/* 8. The .zip files support was added;    												*/
/* 9. The realtime feedback agent functionality was added.    							*/
/* For the full list of the current functionality please visit the following web page:	*/
/* http://mediaframe.org/																*/
/*    																					*/
/* 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev							*/
/*																						*/
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* This program is free software; you can redistribute it and/or modify it under the    */
/* terms of the GNU General Public License as published by the Free Software            */
/* Foundation; either version 2 of the License, or (at your option) any later version.  */
/*                                                                                      */
/* This program is distributed in the hope that it will be useful, but WITHOUT ANY      */
/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      */
/* PARTICULAR PURPOSE. See the GNU General Public License for more details.             */
/*                                                                                      */
/* You should have received a copy of the GNU General Public License along with this    */
/* program; (See "LICENSE.GPL"). If not, write to the Free Software Foundation, Inc.,   */
/* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                            */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* If the program runs as Java applet it isn't "interactive" in the sense of the GNU    */
/* General Public License. So paragraph 2c doesn't apply.                               */
/*                                                                                      */
/****************************************************************************************/

package mediaframe.mpeg1;

import java.io.*;
import java.util.Date;
import java.util.Vector;

/**
 * A <code>DataBuffer</code> object represents a data stream (container of the information data blocks). 
 * It contains methods for storing and reading the information from the stream.
 * 
 * @author Konstantin Belous
 */
public final class DataBuffer {	

	/** 
	 * The type of the stream (video or audio).
	 * @see io_tool#TYPE_AUDIO
	 * @see io_tool#TYPE_VIDEO
	 **/
	private int type = -1;

	/** The vector of the frames. */
	private Vector Frames = new Vector();

	/** The frame index of the current frame in the vector of the frames to read the data. */
	private int currentFrameIndex = 0;

	/** The current data block to write the data. */
	private DataBlock currentWriteBlock = null;

	/** The current data block to read the data. */
	private DataBlock currentReadBlock  = null;

	/** The current frame to write the data. */
	private DataFrame currentWriteFrame = null;

	/** The current frame to read the data. */
	private DataFrame currentReadFrame  = null;

	/** <tt>True</tt>, if the end of the file has been reached. */
	private boolean EOF = false;

	/** The <code>MPEG_reader</code> object (reader of the mpeg stream). */
	private MPEG_Reader reader = null;

	/** The time stamp of the current frame to read. */
	private long _time_stamp = -1; 
	/** The start time of the reading side. */
	private long _start_time = -1;

	/** The last 0-4 information bytes (the last information word). */
	private long   lastWord = 0;
	/** The size the last information word (0-4). */
	private int    lastSize = 0;

	/**
	 * Constructs a <code>DataBuffer</code> with the specified type and <code>MPEG_reader</code>.
	 * @param type the type of the <code>DataBuffer</code> (video or audio).
	 * @param reader the <code>MPEG_reader</code> object (reader of the mpeg stream).
	 */
	public DataBuffer(int type, MPEG_Reader reader) {
		super();
		this.type = type;
		this.reader = reader;
	}
	
	/**
	 * Resets the reader's side to zero state. 
	 */
	public synchronized void initReaderSide() {
		this.EOF = false;
		this._time_stamp = -1;
		this._start_time = -1;
		this.currentFrameIndex = 0;
		this.currentReadBlock = null;
		this.currentReadFrame = null;
	}
/*
	private DataBuffer synchroBuffer = null;

	public void setSynchroBuffer(DataBuffer synchroBuffer) {
		this.synchroBuffer = synchroBuffer;
	}
*/

	/**
	 * Reads the next <code>DataBlock</code> from the movie's buffer.
	 * If necessary waits for the writer process for the next block of data 
	 * and calls the method that starts the "rebuffering" process of the applet 
	 * after the free buffer size is less then the min buffer size.
	 * Sets EOF flag to <tt>true</tt> value if the end of the file has been reached.
	 * @throws EOFException if end of the file has been reached.
	 * @throws InterruptedException if another process interrupts the current process.
	 */
	public final DataBlock readNextBlock() throws java.io.EOFException, InterruptedException {

			
/*
			notify_read();
			
			if(this.type == io_tool.TYPE_AUDIO) {
				while (synchroBuffer !=null && ! synchroBuffer.isEOF() && currentReadFrame != null && synchroBuffer.getCurrentReadFrame() != null && synchroBuffer.getCurrentReadFrame().getTimeStamp() > 0 && currentReadFrame.getTimeStamp() > 0
					&& synchroBuffer.getCurrentReadFrame().getTimeStamp() < (currentReadFrame.getTimeStamp()  - 2000 * 90)) {
						System.out.println("Video: " + synchroBuffer.getCurrentReadFrame().getTimeStamp() + " Audio: " + currentReadFrame.getTimeStamp() + " Saudio: " + (currentReadFrame.getTimeStamp()  - 2000 * 90));
						notify_read();
						System.out.println("Wait for video!");
						synchroBuffer.wait_for_read();
						System.out.println("End wait for video!");
				}
			} else {
				while (synchroBuffer !=null && ! synchroBuffer.isEOF() && currentReadFrame != null && synchroBuffer.getCurrentReadFrame() != null && synchroBuffer.getCurrentReadFrame().getTimeStamp() > 0 && currentReadFrame.getTimeStamp() > 0
					&& synchroBuffer.getCurrentReadFrame().getTimeStamp() < (currentReadFrame.getTimeStamp()  + 2000 * 90)) {
						System.out.println("Audio: " + synchroBuffer.getCurrentReadFrame().getTimeStamp() + " Video: " + currentReadFrame.getTimeStamp() + " Svideo: " + (currentReadFrame.getTimeStamp()  + 2000 * 90));
						notify_read();
						System.out.println("Wait for audio!");
						synchroBuffer.wait_for_read();
						System.out.println("End wait for audio!");
			}
*/
			if(currentReadBlock != null){
				if(currentReadBlock.hasNext()){
					currentReadBlock = currentReadBlock.getNext();
					setNewBlock();
					return currentReadBlock;
				}
			}

			if(currentFrameIndex >= Frames.size()) {

				while(reader != null && ! reader.isEOF() && currentFrameIndex >= Frames.size()) {
/*
					if(this.type == io_tool.TYPE_AUDIO) {
						System.out.println("Audio: Wait for read!");
					} else {
						System.out.println("Video: Wait for read!");
					}
*/
					reader.wait_for_read();
				}

				if(currentFrameIndex >= Frames.size()) {
					EOF = true;
					throw new java.io.EOFException("End of stream is reached!");
				}
				
			}
 
			currentReadFrame = (DataFrame)Frames.elementAt(currentFrameIndex);
			currentReadBlock = currentReadFrame.getFirstDataBlock();
			currentFrameIndex++;
			setNewBlock();
			Thread.yield();
			
/*
				playsize += currentReadFrame.size;

				String type = "";
				switch (currentReadFrame.getType()) {
					case DataFrame.I_FRAME: 
						type = "I_FRAME"; break;
					case DataFrame.P_FRAME: 
						type = "P_FRAME"; break;
					case DataFrame.B_FRAME: 
						type = "B_FRAME"; break;
					case DataFrame.TYPE_VIDEO: 
						type = "TYPE_VIDE"; break;
					case DataFrame.PACK_START: 
						type = "PACK_START"; break;
					case DataFrame.SLICE_START: 
						type = "SLICE_START"; break;
					case DataFrame.SYSTEM_START: 
						type = "SYSTEM_START"; break;
					case DataFrame.TYPE_AUDIO: 
						type = "TYPE_AUDIO"; break;
					case DataFrame.SEQ_START: 
						type = "SEQ_START"; break;
					case DataFrame.GOP_START: 
						type = "GOP_START"; break;
					default:
						type = "Unknown (" + currentReadFrame.getType() + ")";
				}

				System.out.println();
				System.out.println(" Type = " + type + " size = " + currentReadFrame.size + " total = " + playsize);
				System.out.println();
*/

			if(this.type == io_tool.TYPE_VIDEO) {
				long timeStamp = currentReadFrame.getTimeStamp();
				if (timeStamp>=0) {
					_time_stamp = timeStamp;
					if (_start_time == -1)
						_start_time = System.currentTimeMillis() - timeStamp / 90 + 1000; // 1 sec buffer
				}
			}

			return currentReadBlock;
	}

	/**
	 * Adds the length of the block to the total amount of the readed bytes. 
	 * Starts the "rebuffering" process of the applet if the free buffer size is less then the min buffer size. 
	 */
	private void setNewBlock() {
		if(this.type == io_tool.TYPE_AUDIO)
			return;
		synchronized(reader) {
			// re-buffering support
			if (reader.getState() == MPEG_Reader.READING
				&& (reader.loadBytes - currentReadFrame.getFilePosition()) < reader.minBufferSize)  {
						reader.startRebuffering();
				}
			}
	}

	/**
	 * Gets the current frame to read.
	 * @return the current frame to read.
	 */
	public DataFrame getCurrentReadFrame() {
		return currentReadFrame;
	}

	/**
	 * Puts the next information byte into the buffer of movie's data.
	 * @param Byte the next information byte to put.
	 */
	public void write(int Byte) {
		if(currentWriteBlock != null){
			synchronized (reader) {
				if(currentWriteBlock.isFull()){
					currentWriteBlock = new DataBlock(currentWriteBlock);
				}
				currentWriteBlock.write(Byte);
				currentWriteFrame.size++;
				reader.loadBytes++;
				if(reader.getState() == MPEG_Reader.BUFFERING) {
					if(reader.loadBytes >= reader.maxBufferSize)
						reader.endOfBuffering();
					else {
						printPercent(
							100 * reader.loadBytes / reader.maxBufferSize
						);
					}
				} else if(reader.getState() == MPEG_Reader.REBUFFERING) {
					if(reader.loadBytes >= reader.maxBufferSize)
						reader.endOfReBuffering();
					else {
						printPercent(
							100 * (reader.loadBytes - (reader.maxBufferSize - reader.reBufferingSize) ) / reader.reBufferingSize
						);
					}
				}
			}
		}
	}
	
	/** 
	 * Prints the current "buffering" percent.
	 * @param currentPercent the current percent.
	 */
	public void printPercent(int currentPercent) {
		long currentTime = -1;
		if((reader.printedPercent < currentPercent) 
		   && ((reader.lastPrintedTime == -1) 
		   || (((currentTime = new Date().getTime()) - reader.lastPrintedTime) >= 100))) {
		   	reader.lastPrintedTime = currentTime;
			reader.videoPlayer.printBufferingPercent(reader, reader.printedPercent = currentPercent);
		}
	}

	/**
	 * Saves the current frame into the buffer.
	 * Gets the last 0-3 bytes from it and adds it to the list of frames.
	 */
	public void saveCurrentFrame() {
		synchronized (this) {
			currentWriteBlock.close();
			lastWord = currentWriteBlock.lastWord;
			lastSize = currentWriteBlock.lastSize;
			Frames.addElement(currentWriteFrame);
			currentWriteFrame = null;
			currentWriteBlock = null;
		}
	}

	/**
	 * Saves the current frame into the buffer as a last frame in the movie file.
	 * Gets the last 0-3 bytes from it and adds it to the list of frames.
	 */
	public void saveLastFrame() {
		synchronized (this) {
			new DataBlock(currentWriteBlock);
			Frames.addElement(currentWriteFrame);
			currentWriteFrame = null;
			currentWriteBlock = null;
		}
	}

	/**
	 * Returns end of file flag.
	 * @return true, if the end of the file has been reached. 
	 */
	public boolean isEOF() {
		return EOF;
	}

	/**
	 * Registers the new frame to read movie's data. 
	 * @param newFrame the frame to register.
	 */
	public void registerNewFrame(DataFrame newFrame) {

		synchronized (this) {
			if(currentWriteBlock != null) {
				currentWriteBlock.close();
				System.out.println("The Block is canceled!");
			}

			if(currentWriteFrame != null) {
				System.out.println("The Frame is canceled!");
			}

			currentWriteFrame = newFrame;
			currentWriteBlock = newFrame.getFirstDataBlock();
			currentWriteBlock.setSize(lastSize);
			currentWriteBlock.setFirstWord(lastWord);
		}
	}
/*
        // Function to replace io_tool.getPts()...
        public long getTimeStamp() {

		return _time_stamp / 90;
        }

        public long getStartTime() {

                return _start_time;
        }

*/

	/**
	 * Adds the specified value to the start playing time.
	 * @param wait the value to add.
	 */
	public void addToStartTime(long wait) {

		_start_time += wait;
	}

	/**
	 * Sets the start playing time to the specified value.
	 * @param time the start playing time value to set.
	 */
	public void setStartTime(long time) {

		_start_time = time;
	}

	/**
	 * Returns time in milliseconds callibrated from the start of the movie.
	 * @return current playing time in milliseconds. 
	 */ 
	public long getCurrentTime() {
		if (_start_time > -1)
			return System.currentTimeMillis() - _start_time;
		else
			return 0;
	}	
	
	/**
	 * Gets current position of the buffer.
	 * @return current buffer position.
	 */
	public int getBufferPosition() {
		return reader.loadBytes;
	}	
}